// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using System.Text;

using Debug = System.Diagnostics.Debug;

namespace Internal.TypeSystem.Ecma
{
    public partial class EcmaModule : ModuleDesc
    {
        private readonly PEReader _peReader;
        protected readonly MetadataReader _metadataReader;
        private volatile bool _isWrapNonExceptionThrowsComputed;
        private volatile bool _isWrapNonExceptionThrows;

        internal interface IEntityHandleObject
        {
            EntityHandle Handle
            {
                get;
            }
        }

        private sealed class EcmaObjectLookupWrapper : IEntityHandleObject
        {
            private readonly EntityHandle _handle;
            private readonly object _obj;

            public EcmaObjectLookupWrapper(EntityHandle handle, object obj)
            {
                _obj = obj;
                _handle = handle;
            }

            public EntityHandle Handle
            {
                get
                {
                    return _handle;
                }
            }

            public object Object
            {
                get
                {
                    return _obj;
                }
            }
        }

        internal sealed class EcmaObjectLookupHashtable : LockFreeReaderHashtable<EntityHandle, IEntityHandleObject>
        {
            private readonly EcmaModule _module;

            public EcmaObjectLookupHashtable(EcmaModule module)
            {
                _module = module;
            }

            protected override int GetKeyHashCode(EntityHandle key)
            {
                return key.GetHashCode();
            }

            protected override int GetValueHashCode(IEntityHandleObject value)
            {
                return value.Handle.GetHashCode();
            }

            protected override bool CompareKeyToValue(EntityHandle key, IEntityHandleObject value)
            {
                return key.Equals(value.Handle);
            }

            protected override bool CompareValueToValue(IEntityHandleObject value1, IEntityHandleObject value2)
            {
                if (ReferenceEquals(value1, value2))
                    return true;
                else
                    return value1.Handle.Equals(value2.Handle);
            }

            protected override IEntityHandleObject CreateValueFromKey(EntityHandle handle)
            {
                object item;
                switch (handle.Kind)
                {
                    case HandleKind.TypeDefinition:
                        item = new EcmaType(_module, (TypeDefinitionHandle)handle);
                        break;

                    case HandleKind.MethodDefinition:
                        {
                            MethodDefinitionHandle methodDefinitionHandle = (MethodDefinitionHandle)handle;
                            TypeDefinitionHandle typeDefinitionHandle = _module._metadataReader.GetMethodDefinition(methodDefinitionHandle).GetDeclaringType();
                            EcmaType type = _module.GetType(typeDefinitionHandle);
                            item = new EcmaMethod(type, methodDefinitionHandle);
                        }
                        break;

                    case HandleKind.FieldDefinition:
                        {
                            FieldDefinitionHandle fieldDefinitionHandle = (FieldDefinitionHandle)handle;
                            TypeDefinitionHandle typeDefinitionHandle = _module._metadataReader.GetFieldDefinition(fieldDefinitionHandle).GetDeclaringType();
                            EcmaType type = _module.GetType(typeDefinitionHandle);
                            item = new EcmaField(type, fieldDefinitionHandle);
                        }
                        break;

                    case HandleKind.TypeReference:
                        item = _module.ResolveTypeReference((TypeReferenceHandle)handle);
                        break;

                    case HandleKind.MemberReference:
                        item = _module.ResolveMemberReference((MemberReferenceHandle)handle);
                        break;

                    case HandleKind.AssemblyReference:
                        item = _module.ResolveAssemblyReference((AssemblyReferenceHandle)handle);
                        break;

                    case HandleKind.TypeSpecification:
                        item = _module.ResolveTypeSpecification((TypeSpecificationHandle)handle);
                        break;

                    case HandleKind.MethodSpecification:
                        item = _module.ResolveMethodSpecification((MethodSpecificationHandle)handle);
                        break;

                    case HandleKind.ExportedType:
                        item = _module.ResolveExportedType((ExportedTypeHandle)handle);
                        break;

                    case HandleKind.StandaloneSignature:
                        item = _module.ResolveStandaloneSignature((StandaloneSignatureHandle)handle);
                        break;

                    case HandleKind.ModuleDefinition:
                        // ECMA-335 Partition 2 II.22.38 1d: This should not occur in a CLI ("compressed metadata") module,
                        // but resolves to "current module".
                        item = _module;
                        break;

                    case HandleKind.ModuleReference:
                        item = _module.ResolveModuleReference((ModuleReferenceHandle)handle);
                        break;

                    default:
                        ThrowHelper.ThrowBadImageFormatException();
                        item = null;
                        break;
                }

                switch (handle.Kind)
                {
                    case HandleKind.TypeDefinition:
                    case HandleKind.MethodDefinition:
                    case HandleKind.FieldDefinition:
                        // type/method/field definitions directly correspond to their target item.
                        return (IEntityHandleObject)item;
                    default:
                        // Everything else is some form of reference which cannot be self-describing
                        return new EcmaObjectLookupWrapper(handle, item);
                }
            }
        }

        private ModuleDesc ResolveModuleReference(ModuleReferenceHandle handle)
        {
            ModuleReference moduleReference = _metadataReader.GetModuleReference(handle);
            string fileName = _metadataReader.GetString(moduleReference.Name);

            return _moduleResolver.ResolveModule(this.Assembly, fileName);
        }

        private readonly LockFreeReaderHashtable<EntityHandle, IEntityHandleObject> _resolvedTokens;
        private readonly IModuleResolver _moduleResolver;

        internal EcmaModule(TypeSystemContext context, PEReader peReader, MetadataReader metadataReader, IAssemblyDesc containingAssembly, IModuleResolver customModuleResolver)
            : base(context, containingAssembly)
        {
            _peReader = peReader;
            _metadataReader = metadataReader;
            _resolvedTokens = new EcmaObjectLookupHashtable(this);
            _moduleResolver = customModuleResolver ?? context;
        }

        public static EcmaModule Create(TypeSystemContext context, PEReader peReader, IAssemblyDesc containingAssembly, IModuleResolver customModuleResolver = null)
        {
            MetadataReader metadataReader = CreateMetadataReader(context, peReader);

            if (containingAssembly == null)
                return new EcmaAssembly(context, peReader, metadataReader, customModuleResolver);
            else
                return new EcmaModule(context, peReader, metadataReader, containingAssembly, customModuleResolver);
        }

        private static MetadataReader CreateMetadataReader(TypeSystemContext context, PEReader peReader)
        {
            if (!peReader.HasMetadata)
            {
                ThrowHelper.ThrowBadImageFormatException();
            }

            var stringDecoderProvider = context as IMetadataStringDecoderProvider;

            MetadataReader metadataReader = peReader.GetMetadataReader(MetadataReaderOptions.None /* MetadataReaderOptions.ApplyWindowsRuntimeProjections */,
                stringDecoderProvider?.GetMetadataStringDecoder());

            return metadataReader;
        }

        public PEReader PEReader
        {
            get
            {
                return _peReader;
            }
        }

        public MetadataReader MetadataReader
        {
            get
            {
                return _metadataReader;
            }
        }

        /// <summary>
        /// Gets the managed entrypoint method of this module or null if the module has no managed entrypoint.
        /// </summary>
        public MethodDesc EntryPoint
        {
            get
            {
                CorHeader corHeader = _peReader.PEHeaders.CorHeader;
                if ((corHeader.Flags & CorFlags.NativeEntryPoint) != 0)
                {
                    // Entrypoint is an RVA to an unmanaged method
                    return null;
                }

                int entryPointToken = corHeader.EntryPointTokenOrRelativeVirtualAddress;
                if (entryPointToken == 0)
                {
                    // No entrypoint
                    return null;
                }

                EntityHandle handle = MetadataTokens.EntityHandle(entryPointToken);

                if (handle.Kind == HandleKind.MethodDefinition)
                {
                    return GetMethod(handle);
                }
                else if (handle.Kind == HandleKind.AssemblyFile)
                {
                    // Entrypoint not in the manifest assembly
                    throw new NotImplementedException();
                }

                // Bad metadata
                ThrowHelper.ThrowBadImageFormatException();
                return null;
            }
        }

        public bool IsPlatformNeutral
        {
            get
            {
                PEHeaders peHeaders = PEReader.PEHeaders;
                return peHeaders.PEHeader.Magic == PEMagic.PE32
                    && (peHeaders.CorHeader.Flags & (CorFlags.Prefers32Bit | CorFlags.Requires32Bit)) != CorFlags.Requires32Bit
                    && (peHeaders.CorHeader.Flags & CorFlags.ILOnly) != 0
                    && peHeaders.CoffHeader.Machine == Machine.I386;
            }
        }

        private TypeDefinitionHandle[] _typeDefinitionBuckets;
        private volatile TypeDefinitionHandle[] _typeDefinitionBucketHeads;

        private TypeDefinitionHandle[] InitializeTypeDefinitionBucketHeads()
        {
            TypeDefinitionHandle[] buckets = new TypeDefinitionHandle[_metadataReader.TypeDefinitions.Count + 1];
            TypeDefinitionHandle[] bucketHeads = new TypeDefinitionHandle[(buckets.Length / 8) + 1];

            MetadataReader reader = _metadataReader;
            foreach (TypeDefinitionHandle typeHandle in reader.TypeDefinitions)
            {
                TypeDefinition typeDef = reader.GetTypeDefinition(typeHandle);
                if (typeDef.Attributes.IsNested())
                    continue;

                int hashCode = VersionResilientHashCode.NameHashCode(reader.GetStringBytes(typeDef.Namespace), reader.GetStringBytes(typeDef.Name));

                ref TypeDefinitionHandle head = ref bucketHeads[(uint)hashCode % bucketHeads.Length];
                ref TypeDefinitionHandle entry = ref buckets[MetadataTokens.GetRowNumber(typeHandle)];

                entry = head;
                head = typeHandle;
            }

            _typeDefinitionBuckets = buckets;
            _typeDefinitionBucketHeads = bucketHeads;

            return bucketHeads;
        }

        private TypeDefinitionHandle FindDefinedType(int hashCode, ReadOnlySpan<byte> nameSpace, ReadOnlySpan<byte> name)
        {
            MetadataReader reader = _metadataReader;

            TypeDefinitionHandle[] bucketHeads = _typeDefinitionBucketHeads ?? InitializeTypeDefinitionBucketHeads();
            TypeDefinitionHandle entry = bucketHeads[(uint)hashCode % bucketHeads.Length];
            while (!entry.IsNil)
            {
                var typeDefinition = reader.GetTypeDefinition(entry);
                if (reader.StringEquals(typeDefinition.Name, name) &&
                    reader.StringEquals(typeDefinition.Namespace, nameSpace))
                {
                    return entry;
                }

                entry = _typeDefinitionBuckets[MetadataTokens.GetRowNumber(entry)];
            }

            return default;
        }

        private ExportedTypeHandle[] _exportedTypeBuckets;
        private volatile ExportedTypeHandle[] _exportedTypeBucketHeads;

        private ExportedTypeHandle[] InitializeExportedTypeBucketHeads()
        {
            ExportedTypeHandle[] buckets = new ExportedTypeHandle[_metadataReader.ExportedTypes.Count + 1];
            ExportedTypeHandle[] bucketHeads = new ExportedTypeHandle[(buckets.Length / 8) + 1];

            MetadataReader reader = _metadataReader;
            foreach (ExportedTypeHandle typeHandle in reader.ExportedTypes)
            {
                ExportedType exportDef = reader.GetExportedType(typeHandle);
                if (exportDef.Implementation.Kind == HandleKind.ExportedType)
                    continue;

                int hashCode = VersionResilientHashCode.NameHashCode(reader.GetStringBytes(exportDef.Namespace), reader.GetStringBytes(exportDef.Name));

                ref ExportedTypeHandle head = ref bucketHeads[(uint)hashCode % bucketHeads.Length];
                ref ExportedTypeHandle entry = ref buckets[MetadataTokens.GetRowNumber(typeHandle)];

                entry = head;
                head = typeHandle;
            }

            _exportedTypeBuckets = buckets;
            _exportedTypeBucketHeads = bucketHeads;

            return bucketHeads;
        }

        private ExportedTypeHandle FindExportedType(int hashCode, ReadOnlySpan<byte> nameSpace, ReadOnlySpan<byte> name)
        {
            MetadataReader reader = _metadataReader;

            ExportedTypeHandle[] bucketHeads = _exportedTypeBucketHeads ?? InitializeExportedTypeBucketHeads();
            ExportedTypeHandle entry = bucketHeads[(uint)hashCode % bucketHeads.Length];
            while (!entry.IsNil)
            {
                var exportedType = reader.GetExportedType(entry);
                if (reader.StringEquals(exportedType.Name, name) &&
                    reader.StringEquals(exportedType.Namespace, nameSpace))
                {
                    return entry;
                }

                entry = _exportedTypeBuckets[MetadataTokens.GetRowNumber(entry)];
            }

            return default;
        }

        public sealed override object GetType(ReadOnlySpan<byte> nameSpace, ReadOnlySpan<byte> name, NotFoundBehavior notFoundBehavior)
        {
            int hashCode = VersionResilientHashCode.NameHashCode(nameSpace, name);

            var currentModule = this;
            // src/coreclr/vm/clsload.cpp use the same restriction to detect a loop in the type forwarding.
            for (int typeForwardingChainSize = 0; typeForwardingChainSize <= 1024; typeForwardingChainSize++)
            {
                TypeDefinitionHandle typeDefHandle = currentModule.FindDefinedType(hashCode, nameSpace, name);
                if (!typeDefHandle.IsNil)
                    return currentModule.GetType(typeDefHandle);

                ExportedTypeHandle exportedTypeHandle = currentModule.FindExportedType(hashCode, nameSpace, name);
                if (!exportedTypeHandle.IsNil)
                {
                    ExportedType exportedType = currentModule._metadataReader.GetExportedType(exportedTypeHandle);
                    if (exportedType.IsForwarder)
                    {
                        object implementation = currentModule.GetObject(exportedType.Implementation, notFoundBehavior);

                        if (implementation == null)
                        {
                            return null;
                        }
                        else if (implementation is EcmaModule ecmaModule)
                        {
                            currentModule = ecmaModule;
                        }
                        else if (implementation is ModuleDesc moduleDesc)
                        {
                            return moduleDesc.GetType(nameSpace, name, notFoundBehavior);
                        }
                        else if (implementation is ResolutionFailure)
                        {
                            // No need to check notFoundBehavior - the callee already handled ReturnNull and Throw
                            return implementation;
                        }
                        else
                        {
                            // TODO
                            throw new NotImplementedException();
                        }
                    }
                    else
                    {
                        // TODO:
                        throw new NotImplementedException();
                    }
                }
            }

            if (notFoundBehavior != NotFoundBehavior.ReturnNull)
            {
                var failure = ResolutionFailure.GetTypeLoadResolutionFailure(Encoding.UTF8.GetString(nameSpace.ToArray()), Encoding.UTF8.GetString(name.ToArray()), this);
                if (notFoundBehavior == NotFoundBehavior.Throw)
                    failure.Throw();

                return failure;
            }

            return null;
        }

        public TypeDesc GetType(EntityHandle handle)
        {
            TypeDesc type = GetObject(handle, NotFoundBehavior.Throw) as TypeDesc;
            if (type == null)
                ThrowHelper.ThrowBadImageFormatException();
            return type;
        }

        public EcmaType GetType(TypeDefinitionHandle handle)
        {
            return (EcmaType)GetType((EntityHandle)handle);
        }

        public MethodDesc GetMethod(EntityHandle handle)
        {
            MethodDesc method = GetObject(handle, NotFoundBehavior.Throw) as MethodDesc;
            if (method == null)
                ThrowHelper.ThrowBadImageFormatException();
            return method;
        }

        public EcmaMethod GetMethod(MethodDefinitionHandle handle)
        {
            return (EcmaMethod)GetMethod((EntityHandle)handle);
        }

        public FieldDesc GetField(EntityHandle handle)
        {
            FieldDesc field = GetObject(handle, NotFoundBehavior.Throw) as FieldDesc;
            if (field == null)
                ThrowHelper.ThrowBadImageFormatException();
            return field;
        }

        public EcmaField GetField(FieldDefinitionHandle handle)
        {
            return (EcmaField)GetField((EntityHandle)handle);
        }

        internal EcmaField GetField(FieldDefinitionHandle handle, EcmaType owningType)
        {
            if (!_resolvedTokens.TryGetValue(handle, out IEntityHandleObject result))
            {
                Debug.Assert(_metadataReader.GetFieldDefinition(handle).GetDeclaringType() == owningType.Handle);
                result = _resolvedTokens.AddOrGetExisting(new EcmaField(owningType, handle));
            }
            return (EcmaField)result;
        }

        internal EcmaMethod GetMethod(MethodDefinitionHandle handle, EcmaType owningType)
        {
            if (!_resolvedTokens.TryGetValue(handle, out IEntityHandleObject result))
            {
                Debug.Assert(_metadataReader.GetMethodDefinition(handle).GetDeclaringType() == owningType.Handle);
                result = _resolvedTokens.AddOrGetExisting(new EcmaMethod(owningType, handle));
            }
            return (EcmaMethod)result;
        }

        public object GetObject(EntityHandle handle, NotFoundBehavior notFoundBehavior = NotFoundBehavior.Throw)
        {
            IEntityHandleObject obj = _resolvedTokens.GetOrCreateValue(handle);
            if (obj is EcmaObjectLookupWrapper)
            {
                object result = ((EcmaObjectLookupWrapper)obj).Object;
                if ((result is ResolutionFailure failure) && (notFoundBehavior != NotFoundBehavior.ReturnResolutionFailure))
                {
                    if (notFoundBehavior == NotFoundBehavior.ReturnNull)
                        return null;
                    else
                        failure.Throw();
                }
                return result;
            }
            else
            {
                return obj;
            }
        }

        private object ResolveMethodSpecification(MethodSpecificationHandle handle)
        {
            MethodSpecification methodSpecification = _metadataReader.GetMethodSpecification(handle);

            object resolvedMethod = GetObject(methodSpecification.Method, NotFoundBehavior.ReturnResolutionFailure);
            if (resolvedMethod is ResolutionFailure)
                return resolvedMethod;

            MethodDesc methodDef = resolvedMethod as MethodDesc;
            if (methodDef == null)
                ThrowHelper.ThrowBadImageFormatException();

            BlobReader signatureReader = _metadataReader.GetBlobReader(methodSpecification.Signature);
            EcmaSignatureParser parser = new EcmaSignatureParser(this, signatureReader, NotFoundBehavior.ReturnResolutionFailure);

            TypeDesc[] instantiation = parser.ParseMethodSpecSignature();

            if (instantiation == null)
                return parser.ResolutionFailure;

            return Context.GetInstantiatedMethod(methodDef, new Instantiation(instantiation));
        }

        private object ResolveStandaloneSignature(StandaloneSignatureHandle handle)
        {
            StandaloneSignature signature = _metadataReader.GetStandaloneSignature(handle);
            BlobReader signatureReader = _metadataReader.GetBlobReader(signature.Signature);
            EcmaSignatureParser parser = new EcmaSignatureParser(this, signatureReader, NotFoundBehavior.ReturnResolutionFailure);

            MethodSignature methodSig = parser.ParseMethodSignature();
            if (methodSig == null)
                return parser.ResolutionFailure;
            return methodSig;
        }

        private object ResolveTypeSpecification(TypeSpecificationHandle handle)
        {
            TypeSpecification typeSpecification = _metadataReader.GetTypeSpecification(handle);

            BlobReader signatureReader = _metadataReader.GetBlobReader(typeSpecification.Signature);
            EcmaSignatureParser parser = new EcmaSignatureParser(this, signatureReader, NotFoundBehavior.ReturnResolutionFailure);

            TypeDesc parsedType = parser.ParseType();
            if (parsedType == null)
                return parser.ResolutionFailure;
            else
                return parsedType;
        }

        private object ResolveMemberReference(MemberReferenceHandle handle)
        {
            MemberReference memberReference = _metadataReader.GetMemberReference(handle);

            object parent = GetObject(memberReference.Parent, NotFoundBehavior.ReturnResolutionFailure);

            if (parent is ResolutionFailure)
                return parent;

            TypeDesc parentTypeDesc = parent as TypeDesc;
            if (parentTypeDesc != null)
            {
                BlobReader signatureReader = _metadataReader.GetBlobReader(memberReference.Signature);

                EcmaSignatureParser parser = new EcmaSignatureParser(this, signatureReader, NotFoundBehavior.ReturnResolutionFailure);

                ReadOnlySpan<byte> name = _metadataReader.GetStringBytes(memberReference.Name);

                if (parser.IsFieldSignature)
                {
                    FieldDesc field = parentTypeDesc.GetField(name);
                    if (field != null)
                        return field;

                    return ResolutionFailure.GetMissingFieldFailure(parentTypeDesc, System.Text.Encoding.UTF8.GetString(name.ToArray()));
                }
                else
                {
                    MethodSignature sig = parser.ParseMethodSignature();
                    if (sig == null)
                        return parser.ResolutionFailure;
                    TypeDesc typeDescToInspect = parentTypeDesc;
                    Instantiation substitution = default(Instantiation);

                    // Try to resolve the name and signature in the current type, or any of the base types.
                    do
                    {
                        MethodDesc method = typeDescToInspect.GetMethod(name, sig, substitution);
                        if (method == null && Context.SupportsTypeEquivalence)
                            method = typeDescToInspect.GetMethodWithEquivalentSignature(name, sig, substitution);
                        if (method != null)
                        {
                            // If this resolved to one of the base types, make sure it's not a constructor.
                            // Instance constructors are not inherited.
                            if (typeDescToInspect != parentTypeDesc && method.IsConstructor)
                                break;

                            return method;
                        }
                        var baseType = typeDescToInspect.BaseType;
                        if (baseType != null)
                        {
                            if (!baseType.HasInstantiation)
                            {
                                substitution = default(Instantiation);
                            }
                            else
                            {
                                // If the base type is generic, any signature match for methods on the base type with the generic details from
                                // the deriving type
                                Instantiation newSubstitution = typeDescToInspect.GetTypeDefinition().BaseType.Instantiation;
                                if (!substitution.IsNull)
                                {
                                    TypeDesc[] newSubstitutionTypes = new TypeDesc[newSubstitution.Length];
                                    for (int i = 0; i < newSubstitution.Length; i++)
                                    {
                                        newSubstitutionTypes[i] = newSubstitution[i].InstantiateSignature(substitution, default(Instantiation));
                                    }
                                    newSubstitution = new Instantiation(newSubstitutionTypes);
                                }
                                substitution = newSubstitution;
                            }
                        }
                        typeDescToInspect = baseType;
                    } while (typeDescToInspect != null);

                    return ResolutionFailure.GetMissingMethodFailure(parentTypeDesc, System.Text.Encoding.UTF8.GetString(name.ToArray()), sig);
                }
            }
            else if (parent is MethodDesc)
            {
                ThrowHelper.ThrowInvalidProgramException(ExceptionStringID.InvalidProgramVararg, (MethodDesc)parent);
            }
            else if (parent is ModuleDesc)
            {
                throw new NotImplementedException("MemberRef to a global function or variable.");
            }

            ThrowHelper.ThrowBadImageFormatException();
            return null;
        }

        private object ResolveTypeReference(TypeReferenceHandle handle)
        {
            TypeReference typeReference = _metadataReader.GetTypeReference(handle);

            object resolutionScope = GetObject(typeReference.ResolutionScope, NotFoundBehavior.ReturnResolutionFailure);
            if (resolutionScope is ResolutionFailure)
            {
                return resolutionScope;
            }

            if (resolutionScope is ModuleDesc)
            {
                return ((ModuleDesc)(resolutionScope)).GetType(_metadataReader.GetStringBytes(typeReference.Namespace), _metadataReader.GetStringBytes(typeReference.Name), NotFoundBehavior.ReturnResolutionFailure);
            }
            else
            if (resolutionScope is MetadataType)
            {
                string typeName = _metadataReader.GetString(typeReference.Name);
                if (!typeReference.Namespace.IsNil)
                    typeName = _metadataReader.GetString(typeReference.Namespace) + "." + typeName;
                MetadataType result = ((MetadataType)(resolutionScope)).GetNestedType(typeName);
                if (result != null)
                    return result;

                return ResolutionFailure.GetTypeLoadResolutionFailure(typeName, ((MetadataType)resolutionScope).Module);
            }

            // TODO
            throw new NotImplementedException();
        }

        private object ResolveAssemblyReference(AssemblyReferenceHandle handle)
        {
            AssemblyReference assemblyReference = _metadataReader.GetAssemblyReference(handle);

            AssemblyNameInfo an = new AssemblyNameInfo
            (
                name: _metadataReader.GetString(assemblyReference.Name),
                version: assemblyReference.Version,
                cultureName: _metadataReader.GetString(assemblyReference.Culture),
                flags: (AssemblyNameFlags)assemblyReference.Flags,
                publicKeyOrToken: _metadataReader.GetBlobContent(assemblyReference.PublicKeyOrToken)
            );

            var assembly = _moduleResolver.ResolveAssembly(an, throwIfNotFound: false);
            if (assembly == null)
                return ResolutionFailure.GetAssemblyResolutionFailure(an.Name);
            else
                return assembly;
        }

        private object ResolveExportedType(ExportedTypeHandle handle)
        {
            ExportedType exportedType = _metadataReader.GetExportedType(handle);

            var implementation = GetObject(exportedType.Implementation, NotFoundBehavior.ReturnResolutionFailure);
            if (implementation is ModuleDesc module)
            {
                ReadOnlySpan<byte> nameSpace = _metadataReader.GetStringBytes(exportedType.Namespace);
                ReadOnlySpan<byte> name = _metadataReader.GetStringBytes(exportedType.Name);
                return module.GetType(nameSpace, name, NotFoundBehavior.ReturnResolutionFailure);
            }
            else
            if (implementation is MetadataType type)
            {
                string name = _metadataReader.GetString(exportedType.Name);
                var nestedType = type.GetNestedType(name);
                if (nestedType == null)
                    return ResolutionFailure.GetTypeLoadResolutionFailure(name, this);
                return nestedType;
            }
            else if (implementation is ResolutionFailure)
            {
                return implementation;
            }
            else
            {
                ThrowHelper.ThrowBadImageFormatException();
                return null;
            }
        }

        public sealed override IEnumerable<EcmaType> GetAllTypes()
        {
            foreach (var typeDefinitionHandle in _metadataReader.TypeDefinitions)
            {
                yield return GetType(typeDefinitionHandle);
            }
        }

        public sealed override EcmaType GetGlobalModuleType()
        {
            int typeDefinitionsCount = _metadataReader.TypeDefinitions.Count;
            if (typeDefinitionsCount == 0)
                return null;

            return (EcmaType)GetType(MetadataTokens.EntityHandle(0x02000001 /* COR_GLOBAL_PARENT_TOKEN */));
        }

        public string GetUserString(UserStringHandle userStringHandle)
        {
            // String literals are not cached
            return _metadataReader.GetUserString(userStringHandle);
        }

        public override string ToString()
        {
            ModuleDefinition moduleDefinition = _metadataReader.GetModuleDefinition();
            return _metadataReader.GetString(moduleDefinition.Name);
        }

        public bool IsWrapNonExceptionThrows
        {
            get
            {
                if (!_isWrapNonExceptionThrowsComputed)
                {
                    ComputeIsWrapNonExceptionThrows();
                    _isWrapNonExceptionThrowsComputed = true;
                }
                return _isWrapNonExceptionThrows;
            }
        }

        private void ComputeIsWrapNonExceptionThrows()
        {
            var reader = MetadataReader;
            var c = reader.StringComparer;
            bool foundAttribute = false;
            foreach (var attr in reader.GetAssemblyDefinition().GetCustomAttributes())
            {
                if (reader.GetAttributeNamespaceAndName(attr, out var ns, out var n))
                {
                    if (c.Equals(ns, "System.Runtime.CompilerServices") && c.Equals(n, "RuntimeCompatibilityAttribute"))
                    {
                        var dec = reader.GetCustomAttribute(attr).DecodeValue(new CustomAttributeTypeProvider(this));

                        foreach (var arg in dec.NamedArguments)
                        {
                            if (arg.Name == "WrapNonExceptionThrows")
                            {
                                if (!(arg.Value is bool))
                                    ThrowHelper.ThrowBadImageFormatException();
                                _isWrapNonExceptionThrows = (bool)arg.Value;
                                foundAttribute = true;
                                break;
                            }
                        }
                    }
                }

                if (foundAttribute)
                {
                    break;
                }
            }
        }
    }
}
